﻿using Microsoft.Office.Server.Search.Administration;
using Microsoft.Office.Server.Search.Administration.Query;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.Query.Rules;
using SPSDeploy.Extensions.Controllers;
using SPSDeploy.Extensions.Extensions;
using SPSDeploy.Extensions.Models;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Xml.Linq;

namespace SPSDeploy.Extensions.Modules.Extract
{
    /// <summary>
    /// Search extract module
    /// </summary>
    internal class SearchModule : BaseModule
    {

        /// <summary>
        /// Initialization
        /// </summary>
        /// <param name="controller"></param>
        internal SearchModule(BaseController controller)
            : base(controller)
        {
        }

        /// <summary>
        /// Execute module
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        internal override XElement Execute(XElement parent)
        {
            foreach (XElement application in parent.Nodes().Where(e => ((XElement)e).Attribute("type").Value == "Search Service Application").Select(e => e))
            {
                SearchServiceApplication serviceApplication = (SearchServiceApplication)ServiceApplicationExtension.GetServiceApplication(application.Attribute("name").Value, application.Attribute("type").Value);
                if (serviceApplication == null)
                    throw new Exception(string.Format("Service application name '{0}' of type '{1}' is not found.", application.Attribute("name").Value, application.Attribute("type").Value));
                SearchService service = Microsoft.Office.Server.Search.Administration.SearchService.Service;
                Content content = new Content(serviceApplication);
                Ranking ranking = new Ranking(serviceApplication, new SearchObjectOwner(SearchObjectLevel.Ssa));
                FederationManager manager = new FederationManager(serviceApplication);
                XElement result = GetSearchApplication(serviceApplication, service, content, ranking);
                AddSearchProxy(result, service);
                AddContentSources(result, content);
                AddCrawlRules(result, content);
                AddCrawlMappings(result, content);
                AddFileTypes(result, content);
                AddCrawlerImpactRules(result, service);
                AddAuthoritativePages(result, ranking);
                AddDemotedSites(result, ranking);
                AddQuerySuggestions(result, serviceApplication);
                AddQueryClientTypes(result, serviceApplication);
                AddResultSources(result, serviceApplication, manager);
                AddResultTypes(result, ResultTypesExtension.GetOwners(serviceApplication), manager, new ResultItemTypeManager(serviceApplication));
                AddQueryRules(result, QueryRulesExtension.GetOwners(serviceApplication), manager, new QueryRuleManager(serviceApplication));
                AddManagedProperties(result, serviceApplication);
                application.Add(result);
                controller.OnProgressMessage(string.Format("Search service application '{0}' information extracted.", application.Attribute("name").Value));
            }
            return parent;
        }

        #region Search Application
        /// <summary>
        /// Return search application
        /// </summary>
        /// <param name="application"></param>
        /// <param name="service"></param>
        /// <param name="content"></param>
        /// <param name="ranking"></param>
        /// <returns></returns>
        private XElement GetSearchApplication(SearchServiceApplication application, SearchService service, Content content, Ranking ranking)
        {
            return new XElement("searchServiceApplication",
                new XAttribute("defaultContentAccessAccount", content.DefaultGatheringAccount),
                new XAttribute("contactEmail", service.ContactEmail),
                new XAttribute("connectionTimeout", service.ConnectionTimeout),
                new XAttribute("acknowledgementTimeout", service.AcknowledgementTimeout),
                new XAttribute("ignoreSSLWarnings", service.IgnoreSSLWarnings),
                new XAttribute("alertsEnabled", application.AlertsEnabled),
                new XAttribute("queryLoggingEnabled", application.QueryLogSettings.QLogEnabled),
                new XAttribute("searchCenterUrl", application.SearchCenterUrl ?? string.Empty),
                new XAttribute("querySuggestionsEnabled", ranking.QuerySuggestionsEnabled));
        }
        #endregion

        #region Search Proxy
        /// <summary>
        /// Add search proxy
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void AddSearchProxy(XElement parent, SearchService service)
        {
            parent.Add(new XElement("proxy",
                new XAttribute("type", service.ProxyType),
                new XAttribute("address", service.WebProxy.Address != null ? service.WebProxy.Address.ToString() : ""),
                new XAttribute("bypassOnLocal", service.WebProxy.BypassProxyOnLocal),
                new XAttribute("bypassList", Tools.GetBypassListText(service.WebProxy.BypassList)),
                new XAttribute("useForFederation", service.UseCrawlProxyForFederation)));
        }
        #endregion

        #region Content Sources
        /// <summary>
        /// Add content sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void AddContentSources(XElement parent, Content content)
        {
            XElement items = new XElement("contentSources");
            foreach (ContentSource item in content.ContentSources)
            {
                XElement element = new XElement("contentSource",
                    new XAttribute("name", item.Name),
                    new XAttribute("crawlPriority", item.CrawlPriority),
                    new XAttribute("type", item.Type.ToString()));
                element.Add(GetSchedule("incrementalCrawlSchedule", item.IncrementalCrawlSchedule));
                element.Add(GetSchedule("fullCrawlSchedule", item.FullCrawlSchedule));
                if (item is SharePointContentSource)
                    element.Add(new XAttribute("continuousCrawl", ((SharePointContentSource)item).EnableContinuousCrawls), new XAttribute("followDirectories", ((SharePointContentSource)item).FollowDirectories), new XAttribute("crawlBehavior", ((SharePointContentSource)item).SharePointCrawlBehavior));
                else if (item is WebContentSource)
                    element.Add(new XAttribute("maxPageDepth", ((WebContentSource)item).MaxPageEnumerationDepth), new XAttribute("maxSiteDepth", ((WebContentSource)item).MaxSiteEnumerationDepth));
                else if (item is FileShareContentSource)
                    element.Add(new XAttribute("followDirectories", ((FileShareContentSource)item).FollowDirectories));
                else if (item is ExchangePublicFolderContentSource)
                    element.Add(new XAttribute("followDirectories", ((ExchangePublicFolderContentSource)item).FollowDirectories));
                else if (item is CustomContentSource)
                    element.Add(new XAttribute("followDirectories", ((CustomContentSource)item).FollowDirectories), new XAttribute("maxPageDepth", ((CustomContentSource)item).MaxPageEnumerationDepth), new XAttribute("maxSiteDepth", ((CustomContentSource)item).MaxSiteEnumerationDepth));
                XElement startAddresses = new XElement("startAddresses");
                foreach (Uri startAddress in item.StartAddresses)
                    startAddresses.Add(new XElement("startAddress", startAddress.ToString()));
                element.Add(startAddresses);
                items.Add(element);
            }
            parent.Add(items);
        }

        /// <summary>
        /// Return search schedule
        /// </summary>
        /// <param name="name"></param>
        /// <param name="schedule"></param>
        /// <returns></returns>
        private XElement GetSchedule(string name, Schedule schedule)
        {
            if (schedule == null)
                return new XElement(name, new XAttribute("type", "None"), new XAttribute("description", "None"));
            XElement result = new XElement(name,
                new XAttribute("description", schedule.Description),
                new XAttribute("begin", string.Format("{0:d4}-{1:d2}-{2:d2}", schedule.BeginYear, schedule.BeginMonth, schedule.BeginDay)),
                new XAttribute("start", string.Format("{0:d2}:{1:d2}", schedule.StartHour, schedule.StartMinute)),
                new XAttribute("repeatInterval", schedule.RepeatInterval),
                new XAttribute("repeatDuration", schedule.RepeatDuration));
            if (schedule is DailySchedule)
                result.Add(new XAttribute("type", "Daily"), new XAttribute("daysInterval", ((DailySchedule)schedule).DaysInterval));
            else if (schedule is WeeklySchedule)
                result.Add(new XAttribute("type", "Weekly"), new XAttribute("weeksInterval", ((WeeklySchedule)schedule).WeeksInterval), new XAttribute("daysOfWeek", ((WeeklySchedule)schedule).DaysOfWeek));
            else if (schedule is MonthlyDateSchedule)
                result.Add(new XAttribute("type", "MonthlyDate"), new XAttribute("daysOfMonth", ((MonthlyDateSchedule)schedule).DaysOfMonth), new XAttribute("monthsOfYear", ((MonthlyDateSchedule)schedule).MonthsOfYear));
            else if (schedule is MonthlyDayOfWeekSchedule)
                result.Add(new XAttribute("type", "MonthlyDayOfWeek"), new XAttribute("daysOfWeek", ((MonthlyDayOfWeekSchedule)schedule).DaysOfWeek), new XAttribute("weeksOfMonth", ((MonthlyDayOfWeekSchedule)schedule).WeeksOfMonth), new XAttribute("monthsOfYear", ((MonthlyDayOfWeekSchedule)schedule).MonthsOfYear));
            return result;
        }
        #endregion

        #region Crawl Rules
        /// <summary>
        /// Add crawl rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void AddCrawlRules(XElement parent, Content content)
        {
            XElement items = new XElement("crawlRules");
            foreach (CrawlRule item in content.CrawlRules)
                items.Add(new XElement("crawlRule",
                    new XAttribute("path", item.Path),
                    new XAttribute("type", item.Type.ToString()),
                    new XAttribute("regularExpression", item.IsAdvancedRegularExpression),
                    new XAttribute("suppressIndexing", item.SuppressIndexing),
                    new XAttribute("followComplexUrls", item.FollowComplexUrls),
                    new XAttribute("crawlAsHttp", item.CrawlAsHttp),
                    new XAttribute("priority", item.Priority),
                    new XAttribute("authenticationType", item.AuthenticationType),
                    new XAttribute("accountName", item.AccountName)));
            parent.Add(items);
        }
        #endregion

        #region Crawl Mappings
        /// <summary>
        /// Add crawl mappings
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void AddCrawlMappings(XElement parent, Content content)
        {
            XElement items = new XElement("crawlMappings");
            foreach (CrawlMapping item in content.CrawlMappings)
                items.Add(new XElement("crawlMapping",
                    new XAttribute("source", item.Source.ToString()),
                    new XAttribute("target", item.Target.ToString())));
            parent.Add(items);
        }
        #endregion

        #region File Types
        /// <summary>
        /// Add file types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="content"></param>
        private void AddFileTypes(XElement parent, Content content)
        {
            XElement items = new XElement("fileTypes");
            foreach (Extension item in content.ExtensionList)
                items.Add(new XElement("fileType", new XAttribute("extension", item.FileExtension)));
            parent.Add(items);
        }
        #endregion

        #region Crawler Impact Rules
        /// <summary>
        /// Add crawler impact rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="service"></param>
        private void AddCrawlerImpactRules(XElement parent, SearchService service)
        {
            XElement items = new XElement("crawlImpactRules");
            foreach (SiteHitRule item in service.SiteHitRules)
                items.Add(new XElement("crawlImpactRule",
                    new XAttribute("site", item.Site),
                    new XAttribute("behavior", item.Behavior),
                    new XAttribute("hitRate", item.HitRate)));
            parent.Add(items);
        }
        #endregion

        #region Authoritative Pages
        /// <summary>
        /// Add authoritative pages
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void AddAuthoritativePages(XElement parent, Ranking ranking)
        {
            XElement items = new XElement("authorityPages");
            foreach (AuthorityPage item in ranking.AuthorityPages.OrderBy(e => e.Level).ThenBy(e => e.Url.ToString()))
                items.Add(new XElement("authorityPage", new XAttribute("url", item.Url.ToString()), new XAttribute("level", item.Level.ToString())));
            parent.Add(items);
        }

        /// <summary>
        /// Add demoted sites
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="ranking"></param>
        private void AddDemotedSites(XElement parent, Ranking ranking)
        {
            XElement items = new XElement("demotedSites");
            foreach (DemotedSite item in ranking.DemotedSites.OrderBy(e => e.Url.ToString()))
                items.Add(new XElement("demotedSite", new XAttribute("url", item.Url.ToString())));
            parent.Add(items);
        }
        #endregion

        #region Query Suggestions
        /// <summary>
        /// Add query suggestions
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void AddQuerySuggestions(XElement parent, SearchServiceApplication application)
        {
            XElement items = new XElement("querySuggestions");
            foreach (Ranking ranking in QuerySuggestionsExtension.GetRankings(application))
                foreach (LanguageResource item in ranking.LanguageResources)
                {
                    AddLanguageResourcePhrases(items, item.QuerySuggestionsAlwaysSuggestList);
                    AddLanguageResourcePhrases(items, item.QuerySuggestionsBlockList);
                    AddLanguageResourcePhrases(items, item.QuerySuggestionsSubstitutions);
                }
            parent.Add(items);
        }

        /// <summary>
        /// Add query suggestion phrase list
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="collectionName"></param>
        /// <param name="elementName"></param>
        /// <param name="list"></param>
        private void AddLanguageResourcePhrases(XElement parent, LanguageResourcePhraseList list)
        {
            foreach (LanguageResourcePhrase item in list)
                parent.Add(new XElement("querySuggestion",
                    new XAttribute("culture", item.Culture.ToString()),
                    new XAttribute("type", item.Type.ToString()),
                    new XAttribute("phrase", item.Phrase),
                    new XAttribute("mapping", item.Mapping),
                    new XAttribute("tenantId", item.TenantId.ToString()),
                    new XAttribute("siteId", item.SiteId.ToString()),
                    new XAttribute("webId", item.WebId.ToString())));
        }
        #endregion

        #region Query Client Types
        /// <summary>
        /// Add query client types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void AddQueryClientTypes(XElement parent, SearchServiceApplication application)
        {
            XElement items = new XElement("queryClientTypes");
            foreach (QueryClientTypeMapping item in new QueryClientTypeManager(application).GetQueryClientTypeMappings(new SearchObjectOwner(SearchObjectLevel.Ssa)))
                items.Add(new XElement("queryClientType",
                    new XAttribute("name", item.ClientType),
                    new XAttribute("throttlingTier", item.ThrottlingTier)));
            parent.Add(items);
        }
        #endregion

        #region Result Sources
        /// <summary>
        /// Add result sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        /// <param name="manager"></param>
        private void AddResultSources(XElement parent, SearchServiceApplication application, FederationManager manager)
        {
            AddResultSources(parent, ResultSourcesExtension.GetOwners(application), manager);
            AddDefaultResultSources(parent, ResultSourcesExtension.GetDefaultResultSourceOwners(application), manager);
        }

        /// <summary>
        /// Add result sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        protected void AddResultSources(XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            XElement items = new XElement("resultSources");
            foreach (SearchObjectOwner owner in owners)
                foreach (Source item in manager.ListSources(new SearchObjectFilter(owner) { IncludeHigherLevel = false, IncludeLowerLevels = false }, true))
                {
                    XElement element = new XElement("resultSource",
                        new XAttribute("name", item.Name),
                        new XAttribute("description", item.Description),
                        new XAttribute("active", item.Active),
                        new XAttribute("providerId", item.ProviderId.ToString()),
                        new XAttribute("url", item.ConnectionUrlTemplate ?? string.Empty),
                        new XAttribute("authenticationType", item.HasPermissionToReadAuthInfo ? item.AuthInfo.AuthenticationType.ToString() : string.Empty),
                        new XAttribute("accountName", item.HasPermissionToReadAuthInfo ? ResultSourcesExtension.GetResultSourceAccountName(item.AuthInfo.Data) : string.Empty),
                        new XAttribute("level", item.Owner.Level),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    element.Add(GetQueryTransform(item.QueryTransform, manager, owner));
                    items.Add(element);
                }
            parent.Add(items);
        }

        /// <summary>
        /// Add default result sources
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        protected void AddDefaultResultSources(XElement parent, List<SearchObjectOwner> owners, FederationManager manager)
        {
            XElement items = new XElement("defaultResultSources");
            foreach (SearchObjectOwner owner in owners)
                AddDefaultResultSourceItem(items, manager.GetDefaultSource(owner), owner, manager);
            parent.Add(items);
        }

        /// <summary>
        /// Add default result source item
        /// </summary>
        /// <param name="items"></param>
        /// <param name="item"></param>
        /// <param name="owner"></param>
        /// <param name="manager"></param>
        private void AddDefaultResultSourceItem(XElement items, Source item, SearchObjectOwner owner, FederationManager manager)
        {
            items.Add(new XElement("defaultResultSource",
                new XAttribute("name", item.Name),
                new XAttribute("level", owner.Level),
                new XAttribute("siteId", owner.SPSiteId.ToString()),
                new XAttribute("webId", owner.SPWebId.ToString())));
        }

        /// <summary>
        /// Return query transform element
        /// </summary>
        /// <param name="queryTransform"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        /// <returns></returns>
        private XElement GetQueryTransform(QueryTransform queryTransform, FederationManager manager, SearchObjectOwner owner)
        {
            XElement result = new XElement("queryTransform");
            if (queryTransform.QueryTemplate != null)
                result.Add(new XAttribute("queryTemplate", queryTransform.QueryTemplate));
            if (queryTransform.SourceId != null && queryTransform.SourceId.Value != Guid.Empty)
            {
                Source item = manager.GetSource(queryTransform.SourceId.Value, owner);
                result.Add(new XElement("source",
                    new XAttribute("name", item.Name),
                    new XAttribute("level", owner.Level),
                    new XAttribute("siteId", owner.SPSiteId.ToString()),
                    new XAttribute("webId", owner.SPWebId.ToString())));
            }
            if (queryTransform.OverrideProperties != null)
            {
                XElement elements = new XElement("overrideProperties");
                foreach (KeyValuePair<string, object> item in queryTransform.OverrideProperties)
                {
                    XElement element = new XElement("overrideProperty", new XAttribute("key", item.Key));
                    AddResultSourceQueryTransformValue(element, item.Value);
                    elements.Add(element);
                }
                Tools.RollupElement(result, elements);
            }
            if (queryTransform.QueryTemplateParameters != null)
            {
                XElement elements = new XElement("queryTemplateParameters");
                foreach (KeyValuePair<string, bool> item in queryTransform.QueryTemplateParameters)
                    elements.Add(new XElement("queryTemplateParameter", new XAttribute("key", item.Key), new XAttribute("value", item.Value)));
                Tools.RollupElement(result, elements);
            }
            return result;
        }

        /// <summary>
        /// Add result source query transform value
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        private void AddResultSourceQueryTransformValue(XElement element, object value)
        {
            if (value is int)
                element.Add(new XAttribute("type", "Int32"), new XAttribute("value", value.ToString()));
            else if (value is string)
                element.Add(new XAttribute("type", "String"), new XAttribute("value", value));
            else if (value is StringCollection)
            {
                element.Add(new XAttribute("type", "StringCollection"));
                XElement values = new XElement("values");
                foreach (string item in (StringCollection)value)
                    values.Add(new XElement("value", item));
                element.Add(values);
            }
            else if (value is SortCollection)
            {
                element.Add(new XAttribute("type", "SortCollection"));
                XElement values = new XElement("values");
                foreach (Sort item in (SortCollection)value)
                    values.Add(new XElement("value", new XAttribute("direction", item.Direction), new XAttribute("property", item.Property)));
                element.Add(values);
            }
            else if (value is ReorderingRuleCollection)
            {
                element.Add(new XAttribute("type", "ReorderingRuleCollection"));
                XElement values = new XElement("values");
                foreach (ReorderingRule item in (ReorderingRuleCollection)value)
                    values.Add(new XElement("value", new XAttribute("boost", item.Boost), new XAttribute("matchType", item.MatchType.ToString()), new XAttribute("matchValue", item.MatchValue)));
                element.Add(values);
            }
            else
                throw new Exception(string.Format("Query transform value type '{0}' is not supported.", value.GetType().Name));
        }
        #endregion

        #region Result Types
        /// <summary>
        /// Add result types
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="federationManager"></param>
        /// <param name="resultItemTypeManager"></param>
        protected void AddResultTypes(XElement parent, List<SearchObjectOwner> owners, FederationManager federationManager, ResultItemTypeManager resultItemTypeManager)
        {
            XElement items = new XElement("resultTypes");
            foreach (SearchObjectOwner owner in owners)
                foreach (ResultItemType item in resultItemTypeManager.GetResultItemTypes(owner, false))
                {
                    XElement element = new XElement("resultType",
                        new XAttribute("name", item.Name),
                        new XAttribute("optimize", item.OptimizeForFrequentUse),
                        new XAttribute("priority", item.RulePriority),
                        new XAttribute("displayTemplateUrl", item.DisplayTemplateUrl),
                        new XAttribute("displayProperties", item.DisplayProperties),
                        new XAttribute("level", item.Owner.Level),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    AddResultTypeSource(element, item.SourceID, federationManager, item.Owner);
                    AddResultTypePropertyRules(element, item.Rules);
                    items.Add(element);
                }
            parent.Add(items);
        }

        /// <summary>
        /// Add result type source
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="sourceId"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void AddResultTypeSource(XElement parent, Guid sourceId, FederationManager manager, SearchObjectOwner owner)
        {
            if (sourceId != Guid.Empty)
            {
                Source item = manager.GetSource(sourceId, owner);
                parent.Add(new XElement("resultSource",
                    new XAttribute("name", item.Name),
                    new XAttribute("level", item.Owner.Level),
                    new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                    new XAttribute("webId", item.Owner.SPWebId.ToString())));
            }
        }

        /// <summary>
        /// Add result type property rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="rules"></param>
        private void AddResultTypePropertyRules(XElement parent, PropertyRuleCollection rules)
        {
            XElement elements = new XElement("rules");
            foreach (PropertyRule item in rules.PropertyRules)
            {
                XElement element = new XElement("rule", new XAttribute("name", item.PropertyName), new XAttribute("operator", item.PropertyOperator.Representation));
                XElement values = new XElement("values");
                foreach (string value in item.PropertyValues)
                    values.Add(new XElement("value", value));
                Tools.RollupElement(element, values);
                elements.Add(element);
            }
            Tools.RollupElement(parent, elements);
        }
        #endregion

        #region Query Rules
        /// <summary>
        /// Add query rules
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="owners"></param>
        /// <param name="manager"></param>
        /// <param name="queryRuleManager"></param>
        protected void AddQueryRules(XElement parent, List<SearchObjectOwner> owners, FederationManager manager, QueryRuleManager queryRuleManager)
        {
            XElement items = new XElement("queryRules");
            foreach (SearchObjectOwner owner in owners)
            {
                SearchObjectFilter filter = new SearchObjectFilter(owner) { IncludeHigherLevel = false, IncludeLowerLevels = false };
                QueryRuleGroupCollection groups = queryRuleManager.GetQueryRuleGroups(filter);
                UserSegmentCollection segments = queryRuleManager.GetUserSegments(filter);
                Microsoft.Office.Server.Search.Query.Rules.BestBetCollection bestBets = queryRuleManager.GetBestBets(filter);
                foreach (QueryRule item in queryRuleManager.GetQueryRules(filter))
                {
                    XElement element = new XElement("queryRule",
                        new XAttribute("name", item.DisplayName),
                        new XAttribute("active", item.IsActive),
                        new XAttribute("startDate", Tools.GetDateValue(item.StartDate)),
                        new XAttribute("endDate", Tools.GetDateValue(item.EndDate)),
                        new XAttribute("reviewDate", Tools.GetDateValue(item.ReviewDate)),
                        new XAttribute("contact", item.Contact),
                        new XAttribute("group", item.GroupId != null ? groups[item.GroupId.Value].Name : string.Empty),
                        new XAttribute("processing", item.ProcessingDirective),
                        new XAttribute("level", item.Owner.Level),
                        new XAttribute("siteId", item.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", item.Owner.SPWebId.ToString()));
                    AddQueryRuleContextConditions(element, item.ContextConditions, manager, owner, segments);
                    AddQueryRuleQueryConditions(element, item.QueryConditions);
                    AddQueryRuleBestBetsAction(element, item.AssignBestBetsAction, bestBets);
                    AddQueryRuleResultBlockActions(element, item.CreateResultBlockActions, manager, owner);
                    AddQueryRuleChangeQueryAction(element, item.ChangeQueryAction, manager, owner);
                    items.Add(element);
                }
            }
            parent.Add(items);
        }

        /// <summary>
        /// Add query rule context conditions
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="contextConditions"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        /// <param name="userSegments"></param>
        private void AddQueryRuleContextConditions(XElement parent, ContextConditionCollection contextConditions, FederationManager manager, SearchObjectOwner owner, UserSegmentCollection userSegments)
        {
            XElement elements = new XElement("contextConditions");
            foreach (ContextCondition item in contextConditions)
                if (item is SourceContextCondition)
                {
                    Source source = manager.GetSource(((SourceContextCondition)item).SourceId, owner);
                    elements.Add(new XElement("contextCondition", new XAttribute("type", "Source"),
                        new XAttribute("name", source.Name),
                        new XAttribute("level", source.Owner.Level),
                        new XAttribute("siteId", source.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", source.Owner.SPWebId.ToString())));
                }
                else if (item is CategoryContextCondition)
                    elements.Add(new XElement("contextCondition", new XAttribute("type", "Category"),
                        new XAttribute("termStoreId", ((CategoryContextCondition)item).TermStoreId),
                        new XAttribute("termSetId", ((CategoryContextCondition)item).TermSetId),
                        new XAttribute("termId", ((CategoryContextCondition)item).TermId)));
                else if (item is UserSegmentContextCondition)
                {
                    UserSegment userSegment = userSegments[((UserSegmentContextCondition)item).UserSegmentId];
                    XElement element = new XElement("contextCondition", new XAttribute("type", "UserSegment"),
                        new XAttribute("name", userSegment.DisplayName),
                        new XAttribute("level", userSegment.Owner.Level),
                        new XAttribute("siteId", userSegment.Owner.SPSiteId.ToString()),
                        new XAttribute("webId", userSegment.Owner.SPWebId.ToString()));
                    XElement terms = new XElement("terms");
                    foreach (Guid term in userSegment.Terms)
                        terms.Add(new XElement("term", term.ToString()));
                    Tools.RollupElement(element, terms);
                    elements.Add(element);
                }
                else if (item is GenericContextCondition)
                    elements.Add(new XElement("contextCondition", new XAttribute("type", "Generic"), new XAttribute("expression", ((GenericContextCondition)item).Expression)));
                else
                    throw new Exception(string.Format("Query rule context condition type '{0}' is not supported.", item.GetType().Name));
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Add query rule query conditions
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="queryConditions"></param>
        private void AddQueryRuleQueryConditions(XElement parent, QueryConditionCollection queryConditions)
        {
            XElement elements = new XElement("queryConditions");
            foreach (QueryCondition item in queryConditions)
            {
                XElement element = new XElement("queryCondition",
                    new XAttribute("match", item.MatchingOptions),
                    new XAttribute("terms", item.SubjectTermsOrigin));
                XElement variables = new XElement("boundVariables");
                foreach (BoundVariableDefinition variable in item.BoundVariables)
                    variables.Add(new XElement("boundVariable", new XAttribute("name", variable.Name), new XAttribute("origin", variable.Origin), new XAttribute("type", variable.Type)));
                Tools.RollupElement(element, variables);
                if (item is KeywordCondition)
                {
                    element.Add(new XAttribute("type", "Keyword"));
                    XElement terms = new XElement("terms");
                    foreach (string term in ((KeywordCondition)item).Terms)
                        terms.Add(new XElement("term", term));
                    Tools.RollupElement(element, terms);
                }
                else if (item is PeopleNameCondition)
                    element.Add(new XAttribute("type", "PeopleName"));
                else if (item is CommonQueryCondition)
                {
                    element.Add(new XAttribute("type", "CommonQuery"), new XAttribute("source", ((CommonQueryCondition)item).SourceName), new XAttribute("threshold", ((CommonQueryCondition)item).Threshold));
                    XElement additions = new XElement("additions");
                    foreach (string addition in ((CommonQueryCondition)item).Additions)
                        additions.Add(new XElement("addition", addition));
                    Tools.RollupElement(element, additions);
                }
                else if (item is CommonlyClickedPropertyCondition)
                {
                    element.Add(new XAttribute("type", "CommonlyClickedProperty"), new XAttribute("property", ((CommonlyClickedPropertyCondition)item).PropertyName), new XAttribute("threshold", ((CommonlyClickedPropertyCondition)item).Threshold));
                    XElement values = new XElement("values");
                    foreach (string value in ((CommonlyClickedPropertyCondition)item).PropertyValues)
                        values.Add(new XElement("value", value));
                    Tools.RollupElement(element, values);
                }
                else if (item is RegularExpressionCondition)
                    element.Add(new XAttribute("type", "RegularExpression"), new XAttribute("pattern", ((RegularExpressionCondition)item).Pattern));
                else if (item is TaxonomyDictionaryCondition)
                    element.Add(new XAttribute("type", "TaxonomyDictionary"), new XAttribute("name", ((TaxonomyDictionaryCondition)item).DisplayName),
                        new XAttribute("termStoreId", ((TaxonomyDictionaryCondition)item).TermStoreId),
                        new XAttribute("termSetId", ((TaxonomyDictionaryCondition)item).TermSetId),
                        new XAttribute("termId", ((TaxonomyDictionaryCondition)item).TermId));
                else
                    throw new Exception(string.Format("Query rule condition type '{0}' is not supported.", item.GetType().Name));
                elements.Add(element);
            }
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Add query rule best bets action
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="bestBetsAction"></param>
        /// <param name="bestBets"></param>
        private void AddQueryRuleBestBetsAction(XElement parent, AssignBestBetsAction bestBetsAction, Microsoft.Office.Server.Search.Query.Rules.BestBetCollection bestBets)
        {
            if (bestBetsAction != null)
            {
                XElement elements = new XElement("bestBetActions");
                foreach (Guid id in bestBetsAction.BestBetIds)
                {
                    Microsoft.Office.Server.Search.Query.Rules.BestBet item = bestBets[id];
                    elements.Add(new XElement("bestBetAction",
                        new XAttribute("title", item.Title),
                        new XAttribute("url", item.Url),
                        new XAttribute("visualBestBet", item.IsVisualBestBet),
                        new XAttribute("description", item.Description)));
                }
                Tools.RollupElement(parent, elements);
            }
        }

        /// <summary>
        /// Add query rule result block actions
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="queryActions"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void AddQueryRuleResultBlockActions(XElement parent, QueryActionCollection<CreateResultBlockAction> queryActions, FederationManager manager, SearchObjectOwner owner)
        {
            if (queryActions != null)
            {
                XElement elements = new XElement("queryActions");
                foreach (CreateResultBlockAction item in queryActions)
                {
                    XElement element = new XElement("queryAction",
                        new XAttribute("title", item.ResultTitle.DefaultLanguageString),
                        new XAttribute("alwaysShow", item.AlwaysShow),
                        new XAttribute("resultTitleUrl", item.ResultTitleUrl),
                        new XAttribute("groupTemplate", item.GroupTemplateId ?? string.Empty),
                        new XAttribute("itemTemplate", item.ItemTemplateId ?? string.Empty));
                    if (item.QueryTransform != null)
                        element.Add(GetQueryTransform(item.QueryTransform, manager, owner));
                    elements.Add(element);
                }
                Tools.RollupElement(parent, elements);
            }
        }

        /// <summary>
        /// Add query rule change query action
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="changeQueryAction"></param>
        /// <param name="manager"></param>
        /// <param name="owner"></param>
        private void AddQueryRuleChangeQueryAction(XElement parent, ChangeQueryAction changeQueryAction, FederationManager manager, SearchObjectOwner owner)
        {
            if (changeQueryAction != null)
            {
                XElement element = new XElement("changeQueryAction");
                if (changeQueryAction.QueryTransform != null)
                    element.Add(GetQueryTransform(changeQueryAction.QueryTransform, manager, owner));
                Tools.RollupElement(parent, element);
            }
        }
        #endregion

        #region Managed Properties
        /// <summary>
        /// Add managed properties
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="application"></param>
        private void AddManagedProperties(XElement parent, SearchServiceApplication application)
        {
            HashSet<int> ids = new HashSet<int>();
            List<ManagedPropertyInfoContainer> properties = new List<ManagedPropertyInfoContainer>();
            foreach (SearchObjectOwner owner in ManagedPropertiesExtension.GetOwners(application))
                foreach (ManagedPropertyInfo item in application.GetManagedProperties(null, int.MaxValue, owner))
                    if (!ids.Contains(item.Pid))
                    {
                        properties.Add(new ManagedPropertyInfoContainer() { Property = item, Owner = owner });
                        ids.Add(item.Pid);
                    }
            AddManagedProperties(parent, properties);
        }

        /// <summary>
        /// Add managed properties
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="properties"></param>
        protected void AddManagedProperties(XElement parent, List<ManagedPropertyInfoContainer> properties)
        {
            XElement items = new XElement("managedProperties");
            foreach (ManagedPropertyInfoContainer item in properties)
            {
                XElement element = new XElement("managedProperty",
                    new XAttribute("name", item.Property.Name),
                    new XAttribute("description", item.Property.Description),
                    new XAttribute("type", item.Property.ManagedType),
                    new XAttribute("searchable", item.Property.Searchable),
                    new XAttribute("queryable", item.Property.Queryable),
                    new XAttribute("retrievable", item.Property.Retrievable),
                    new XAttribute("multipleValues", item.Property.HasMultipleValues),
                    new XAttribute("refinable", item.Property.Refinable),
                    new XAttribute("sortable", item.Property.Sortable),
                    new XAttribute("sortableType", item.Property.SortableType),
                    new XAttribute("safeForAnonymous", item.Property.SafeForAnonymous),
                    new XAttribute("tokenNormalization", item.Property.TokenNormalization),
                    new XAttribute("completeMatching", item.Property.CompleteMatching),
                    new XAttribute("companyExtraction", item.Property.CompanyExtraction),
                    new XAttribute("wordExactExtractionCustom", item.Property.WordExactExtractionCustom),
                    new XAttribute("wordExtractionCustom1", item.Property.WordExtractionCustom1),
                    new XAttribute("wordExtractionCustom2", item.Property.WordExtractionCustom2),
                    new XAttribute("wordExtractionCustom3", item.Property.WordExtractionCustom3),
                    new XAttribute("wordExtractionCustom4", item.Property.WordExtractionCustom4),
                    new XAttribute("wordExtractionCustom5", item.Property.WordExtractionCustom5),
                    new XAttribute("wordPartExactExtractionCustom", item.Property.WordPartExactExtractionCustom),
                    new XAttribute("wordPartExtractionCustom1", item.Property.WordPartExtractionCustom1),
                    new XAttribute("wordPartExtractionCustom2", item.Property.WordPartExtractionCustom2),
                    new XAttribute("wordPartExtractionCustom3", item.Property.WordPartExtractionCustom3),
                    new XAttribute("wordPartExtractionCustom4", item.Property.WordPartExtractionCustom4),
                    new XAttribute("wordPartExtractionCustom5", item.Property.WordPartExtractionCustom5),
                    new XAttribute("context", item.Property.Context),
                    new XAttribute("deleteDisallowed", item.Property.DeleteDisallowed),
                    new XAttribute("enabledForScoping", item.Property.EnabledForScoping),
                    new XAttribute("entityExtractorBitMap", item.Property.EntityExtractorBitMap),
                    new XAttribute("fullTextIndex", item.Property.FullTextIndex ?? string.Empty),
                    new XAttribute("indexOptions", item.Property.IndexOptions),
                    new XAttribute("mappingDisallowed", item.Property.MappingDisallowed),
                    new XAttribute("removeDuplicates", item.Property.RemoveDuplicates),
                    new XAttribute("respectPriority", item.Property.RespectPriority),
                    new XAttribute("updateGroup", item.Property.UpdateGroup ?? string.Empty),
                    new XAttribute("level", item.Owner.Level),
                    new XAttribute("siteId", item.Owner.SPSiteId.ToString()));
                AddManagedPropertyRefinerConfiguration(element, item.Property.RefinerConfiguration);
                AddManagedPropertyAliases(element, item.Property.Aliases);
                AddManagedPropertyMappedCrawledProperties(element, item.Property.MappedCrawledProperties);
                items.Add(element);
            }
            parent.Add(items);
        }

        /// <summary>
        /// Add managed property refiner configuration
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="refinerConfiguration"></param>
        private void AddManagedPropertyRefinerConfiguration(XElement parent, RefinerConfiguration refinerConfiguration)
        {
            if (refinerConfiguration != null)
                parent.Add(new XElement("refinerConfiguration",
                    new XAttribute("type", refinerConfiguration.Type),
                    new XAttribute("anchoring", refinerConfiguration.Anchoring),
                    new XAttribute("cutoffMaxBuckets", refinerConfiguration.CutoffMaxBuckets),
                    new XAttribute("divisor", refinerConfiguration.Divisor),
                    new XAttribute("intervals", refinerConfiguration.Intervals),
                    new XAttribute("resolution", refinerConfiguration.Resolution)));
        }

        /// <summary>
        /// Add managed property aliases
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="aliases"></param>
        private void AddManagedPropertyAliases(XElement parent, List<string> aliases)
        {
            XElement elements = new XElement("aliases");
            if (aliases != null)
                foreach (string item in aliases)
                    elements.Add(new XElement("alias", item));
            Tools.RollupElement(parent, elements);
        }

        /// <summary>
        /// Add managed property mapped crawled properties
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="mappedCrawledProperties"></param>
        private void AddManagedPropertyMappedCrawledProperties(XElement parent, List<CrawledPropertyInfo> mappedCrawledProperties)
        {
            XElement elements = new XElement("crawledProperties");
            if (mappedCrawledProperties != null)
                foreach (CrawledPropertyInfo item in mappedCrawledProperties)
                    elements.Add(new XElement("crawledProperty", new XAttribute("name", item.Name), new XAttribute("category", item.CategoryName)));
            Tools.RollupElement(parent, elements);
        }
        #endregion

    }
}